﻿namespace Storm.Plugins
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;
	using System.Reflection;
	using System.Text;
	using System.Windows.Forms;

	using Storm;
	using Storm.Plugins;
	using Storm.Plugins.Attributes;
	using Storm.Plugins.Classes;
	using Storm.Plugins.Enums;
	using Storm.Plugins.Interfaces;

	/// <summary>
	/// Class for managing plugins.
	/// </summary>
	public class PluginManager
	{
		#region Fields

		private List<IPlugin> _loadedPlugins = new List<IPlugin>();

		private Form _formParent = null;
		private string _pluginPath = "";

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the parent Form in which all plugins will be held.
		/// For example, if a plugin creates a StatusStrip, this specifies what Form
		/// the StatusStrip should be created on. When this property has been set,
		/// reloads all plugins.
		/// </summary>
		public Form FormParent
		{
			get { return _formParent; }
			set
			{
				_formParent = value;
				LoadPlugins();
			}
		}

		/// <summary>
		/// Gets or sets the path to all plugins.
		/// When this property is set, reloads all plugins.
		/// </summary>
		public string PluginPath
		{
			get { return _pluginPath; }
			set
			{
				_pluginPath = value;
				LoadPlugins();
			}
		}

		/// <summary>
		/// Gets a list of all currently loaded plugins.
		/// </summary>
		public List<IPlugin> LoadedPlugins
		{ get { return _loadedPlugins; } }

		#endregion

		#region Methods

		#region Public

		/// <summary>
		/// Returns all Plugin attributes of the specified plugin.
		/// </summary>
		/// <param name="plugin">Plugin to look in.</param>
		/// <returns>Plugin attributes applied to the given plugin.</returns>
		public List<Plugin> GetPluginAttribute(IPlugin plugin)
		{
			List<Plugin> attributes = new List<Plugin>();
			Type type = plugin.Type;

			object[] pAttributes = type.GetCustomAttributes
				(typeof(Plugin), false);

			foreach (object obj in pAttributes)
				attributes.Add(pAttributes[0] as Plugin);

			return attributes;
		}

		/// <summary>
		/// Loads all plugins in the PluginPath directory.
		/// </summary>
		public void LoadPlugins()
		{ LoadPluginsInDirectory(_pluginPath); }

		/// <summary>
		/// Loads all plugins in the specified directory.
		/// </summary>
		/// <param name="directoryPath">Directory to load plugins in.</param>
		public void LoadPluginsInDirectory(string directoryPath)
		{
			string[] files = Directory.GetFiles(directoryPath);

			for (int i = 0; i < files.Length; i++)
				this.LoadPlugin(files[i]);
		}

		/// <summary>
		/// Loads all plugins in the specified file, if it is a dynamic link library.
		/// </summary>
		/// <param name="path">Path to the file that the the method should try to load plugins in.</param>
		public IPlugin LoadPlugin(string path)
		{
			IPlugin plugin = null;

			if (Path.GetExtension(path) == ".dll")
			{
				Assembly a = Assembly.LoadFile(path);
				Type[] types = a.GetTypes();

				Classes.Console.AddMessage("Loading plugin/reference: \"" + path + "\"",
					MessageType.Normal);

				foreach (Type t in types)
				{
					if (t.IsSubclassOf(typeof(IPlugin)) == true ||
						t.GetInterfaces().Contains(typeof(IPlugin)) == true)
					{
						IPlugin currentPlugin = (IPlugin)Activator.CreateInstance(t);
						currentPlugin.Assembly = Path.GetFileName(path);
						currentPlugin.FormParent = _formParent;
						currentPlugin.Icon = Properties.Resources.plugin;
						currentPlugin.PluginManager = this;
						currentPlugin.PluginEnabled = false;
						currentPlugin.Type = t;
						currentPlugin.Path = path;

						plugin = currentPlugin;
						_loadedPlugins.Add(currentPlugin);
					}
					else
					{
						Classes.Console.AddMessage("Found type in plugin \"" +
							path + "\" wasn't a Subclass of IPlugin",
							MessageType.Error);
					}
				}
			}

			return plugin;
		}

		/// <summary>
		/// Toggles a plugin on/off in the parent Form.
		/// Uses the plugin's base Type as enabler Type.
		/// </summary>
		/// <param name="plugin">Plugin to enable/disable.</param>
		/// <param name="enable">Whether to enable or disable the plugin.</param>
		public void EnablePlugin(IPlugin plugin, bool enable)
		{
			Assembly a = Assembly.LoadFile(plugin.Path);
			Type[] types = a.GetTypes();

			foreach (Type t in types)
			{
				if (t.IsSubclassOf(typeof(IPlugin)) == true ||
					t.GetInterfaces().Contains(typeof(IPlugin)) == true)
				{
					this.EnablePlugin(plugin, enable, t);
				}
			}
		}

		/// <summary>
		/// Toggles a plugin on/off in the parent Form.
		/// </summary>
		/// <param name="plugin">Plugin to enable/disable.</param>
		/// <param name="enable">Whether to enable or disable the plugin.</param>
		/// <param name="t">Base plugin Type that the enabler should create the plugin as.</param>
		public void EnablePlugin(IPlugin plugin, bool enable, Type t)
		{
			_formParent.Cursor = Cursors.AppStarting;

			Classes.Console.AddMessage("Attaching plugin to parent Form", MessageType.Normal);
			Control pluginControl = (Control)Activator.CreateInstance(t);
			
			if (_loadedPlugins.Contains(plugin) == false)
				_loadedPlugins.Add(plugin);

			if (enable == true)
			{
				try
				{
					plugin.PluginEnabled = true;
					if (_formParent.Controls.Contains(pluginControl) == false)
					{
						if (pluginControl is Form == false)
							_formParent.Controls.Add(pluginControl);
					}
					else
					{
						Classes.Console.AddMessage("FormParent already contains pluginControl",
							MessageType.Error);
					}
				}
				catch (Exception e)
				{
					Plugin attribute = GetPluginAttribute(plugin)[0];
					throw new Exception("Could not enable plugin: \"" + attribute.Name
						+ "\" - " + e.Message);
				}
			}
			else
			{
				if (_formParent.Controls.Contains(pluginControl) == true)
				{
					plugin.PluginEnabled = false;
					_formParent.Controls.Remove(pluginControl);
				}
				else
					Classes.Console.AddMessage("FormParent doesn't contain pluginControl",
						MessageType.Error);
			}

			Classes.Console.AddMessage("Plugin attached to parent Form", MessageType.Normal);
			_formParent.Cursor = Cursors.Default;
		}

		#endregion

		#endregion

		/// <summary>
		/// Initializes the PluginManager.
		/// </summary>
		public PluginManager()
			: this(null, "")
		{ }

		/// <summary>
		/// Initializes the PluginManager.
		/// </summary>
		/// <param name="parent">FormParent value of the PluginManager.</param>
		public PluginManager(Form parent)
			: this(parent, "")
		{ }

		/// <summary>
		/// Initializes the PluginManager.
		/// </summary>
		/// <param name="pluginPath">Path to the plugins directory.</param>
		public PluginManager(string pluginPath)
			: this(null, pluginPath)
		{ }

		/// <summary>
		/// Initializes the PluginManager.
		/// </summary>
		/// <param name="parent">FormParent value of the PluginManager.</param>
		/// <param name="pluginPath">Path to the plugins directory.</param>
		public PluginManager(Form parent, string pluginPath)
		{
			this._formParent = parent;
			this._pluginPath = pluginPath;
		}
	}
}
